fix: player crash when switching audio language (#89) - #129
Merged
Conversation
Reported by users: the player crashed (or showed an error) as soon as
they picked a different audio language from the track menu during
playback. Two audio-track selection call sites in PlayerScreen.kt built
a `TrackSelectionOverride(group, trackIndex)` after only validating
`track.groupIndex < trackGroups.size` and `group.type == TRACK_TYPE_AUDIO`
\u2014 but not `trackIndex < group.length`. There was also no try/catch
around `exoPlayer.trackSelectionParameters = params.build()`.
Root cause: `AudioTrackInfo` captures `groupIndex` / `trackIndex` at the
moment `onTracksChanged` fires. Between that moment and the user actually
picking a track from the menu (which can be several seconds later), the
player may have re-prepared due to:
- adaptive stream switch
- user switching source from the source picker
- MediaItem rebuild when a new external subtitle is selected
- automatic recovery from a transient network error
After any of those events the track groups layout can change, leaving
the cached `trackIndex` out of bounds for the new group. Media3's
`TrackSelectionOverride` constructor throws `IllegalArgumentException`
and the crash propagates up to the Composable, tearing down the player.
Changes:
- Extract the audio-track selection logic into a single private helper
`applyAudioTrackSelection(exoPlayer, track, audioTracks)` that:
- Wraps the whole operation in try/catch for IllegalArgumentException,
IllegalStateException, and generic Exception.
- Calls `clearOverridesOfType(C.TRACK_TYPE_AUDIO)` before setting the
new override so stale overrides from prior selections don't pin the
player to a no-longer-present track.
- Validates `groupIndex in groups.indices`, `group.type == TRACK_TYPE_AUDIO`,
AND `trackIndex in 0 until group.length` before calling
`TrackSelectionOverride(...)`.
- If the group/track validation fails but the override call site
doesn't throw, still applies the `setPreferredAudioLanguage` hint so
Media3 picks the closest matching track on its own rather than
leaving the user on the wrong language.
- Returns the resolved `selectedAudioIndex` or `null` on failure, so
the caller can keep the previous selection rather than jumping to
an incorrect one.
- Replace both audio-selection call sites (the D-pad menu handler at
~line 1485 and the `onSelectAudio` callback at ~line 2075) with calls
to the helper. Both sites shrank from ~20 lines to 3 lines.
The subtitle-selection paths in the same file (lines 934, 983) already
validate `groupIndex in groups.indices` and have been stable, so they
are not touched in this PR to keep the diff focused on the reported
crash. They can be migrated to a similar helper in a follow-up if
wanted.
Closes #89
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #89.
Fixes the player crash users reported when switching audio language from the track menu during playback. Also removes a second, related bug where switching audio from the D-pad handler path ran duplicated unsafe code.
Root cause
Two audio-track selection code paths in
PlayerScreen.kt(the D-pad menu handler at ~line 1485 and theonSelectAudiocallback at ~line 2075) built aTrackSelectionOverride(group, trackIndex)after only validating:track.groupIndex < trackGroups.size✓group.type == TRACK_TYPE_AUDIO✓track.trackIndex < group.length✗ missingThere was also no
try/catcharoundexoPlayer.trackSelectionParameters = params.build().AudioTrackInfocapturesgroupIndex/trackIndexat the momentonTracksChangedfires. Between that moment and the user actually picking a track from the menu (several seconds later, especially if they first opened the subtitles menu, browsed, then switched to audio), the player may have re-prepared due to:After any of those events the track groups layout can change, leaving the cached
trackIndexout of bounds for the new group. Media3'sTrackSelectionOverrideconstructor throwsIllegalArgumentExceptionand the crash propagates up to the Composable, tearing down the player.Fix
Extracted the audio-track selection logic into a single private helper
applyAudioTrackSelection(exoPlayer, track, audioTracks): Int?with proper defense:Key defensive additions:
params.build()assignment could also throw on some devices when the player is in an invalid state.clearOverridesOfType(C.TRACK_TYPE_AUDIO)first — stale overrides from prior selections no longer pin the player to a track index that may not exist in the new group layout.trackIndex in 0 until group.lengthbounds check — this is the specific validation that was missing and caused the crash.setPreferredAudioLanguagehint still applies, so Media3 picks the closest matching track on its own rather than leaving the user on the wrong language.Int?— caller uses?.let { selectedAudioIndex = it }so when the selection fails we keep the previous index rather than jumping to an incorrect one.Both audio-selection call sites now shrink from ~20 lines to 3.
Not touched in this PR
The subtitle-selection paths at lines 934 and 983 already validate
groupIndex in groups.indicesand have been stable — they're left alone to keep the diff focused on the reported crash. They could be migrated to a similar helper in a follow-up if desired.Risk
Low. The helper is strictly more defensive than the inlined code it replaces. In the normal case (fresh tracks, user picks immediately), behavior is identical. In the edge case (stale indices after re-prepare), the old code crashed and the new code either silently picks the correct track via language hint, or keeps the previous selection. Either is strictly better than a crash.